package com.xuzmian.demo.kotlin

import org.junit.Test

/**
 * Kotlin 的型变包括协变、逆变、不变三种.
 *
 * 1.声明处型变 是指可在泛型 被声明的地方 指定该泛型的 型变属性(协变/逆变) （Java只可在泛型 被使用的地方 制定型变属性）
 * 2.Kotlin的声明处型变通过 out修饰符 表示该泛型是 协变 的，只能被用来当做 生产者，只能出现在 输出位置。而 in修饰符 表示
 * 该泛型是 逆变 的，只能被用来当做 消费者，只能出现在 输入位置。
 * 3.类型投影 是Kotlin中的 使用处型变,声明式投影 非常方便，但 无法解决一个类既是泛型T的生产者，又是泛型T的消费者的情况。
 */


/**
 * 1.协变 out，它是声明处型变：
 * 一般原则是：当一个类 C 的类型参数 T 被声明为 out 时，它就只能出现在 C 的成员的输出-位置，但回报是 C<Base> 可以安全地作为 C<Derived>的超类。
 * 简而言之，类 C 是在参数 T 上是协变的，或者说 T 是一个协变的类型参数。 你可以认为 C 是 T 的生产者，而不是 T 的消费者。
 * out修饰符称为型变注解，并且由于它在类型参数声明处提供，所以我们称之为「声明处型变」。 这与 Java 的使用处型变相反，其类型用途通配符使得类型协变。
 */
interface Production<out T> {
    fun produce(): T

    /**
     * fun consume(t:T)   error  范型T不能作为入参类型，对于协变的类型，通常是不允许将泛型类型作为传入参数的类型的，
     * 或者说，对于协变类型，通常是不允许其涉及泛型参数的部分被改变的。
     * 实际上有些情况下，不得已需要在协变的情况下使用泛型参数类型作为方法参数的类型，就可以用 @UnsafeVariance 来告诉编译器，不作限制
     */
    fun consume(t: @UnsafeVariance T){}
}

/**
 * 2.逆变 in，它是声明处型变： Kotlin 补充了一个型变注释：in。它使得一个类型参数「逆变」：只可以被消费而不可以被生产。
 */
interface Consumer<in T> {
    fun consume(item: T)
    /**
     * fun producc():T  error 范型T不能作为方法返回类型，对于逆变类型，通常是不允许将泛型类型作为方法返回参数的类型的。
     */
    fun producc(): @UnsafeVariance T? {
        return null
    }
}

/**
 * 3.不变(Invariant),如果既将泛型作为函数参数，又将泛型作为函数的输出，那就既不用 in 或 out。
 *
 */
interface ProductionConsumer<T> {
    fun produce(): T
    fun consume(item: T)
}


open class Food
open class FastFood : Food()
class Burger : FastFood()

// --------------生产者-------------
class FoodStore : Production<Food> {
    override fun produce(): Food {
        println("Produce food")
        return Food()
    }
}

class FastFoodStore : Production<FastFood> {
    override fun produce(): FastFood {
        println("Produce food")
        return FastFood()
    }
}

class InOutBurger : Production<Burger> {
    override fun produce(): Burger {
        println("Produce burger")
        return Burger()
    }
}

/**
 * 对于 out 泛型，能够将使用子类泛型的对象赋值给使用父类泛型的对象,下面这样反过来使用子类:
 * val production1 : Production<Burger> = FoodStore()  // Error
 * val production2 : Production<Burger> = FastFoodStore()  // Error
 * val production3 : Production<Burger> = InOutBurger()
 */
fun testProducter() {
    val production1: Production<Food> = FoodStore()
    val production2: Production<Food> = FastFoodStore()
    val production3: Production<Food> = InOutBurger()
}


//-----------消费者-------------
class Everybody : Consumer<Food> {
    override fun consume(item: Food) {
        println("Eat food")
    }
}

class ModernPeople : Consumer<FastFood> {
    override fun consume(item: FastFood) {
        println("Eat fast food")
    }
}

class American : Consumer<Burger> {
    override fun consume(item: Burger) {
        println("Eat burger")
    }
}

/**
 * 对于 in 泛型，可以将使用父类泛型的对象赋值给使用子类泛型的对象。反过来使用父类:
 * val consumer1 : Consumer<Food> = Everybody()
 * val consumer2 : Consumer<Food> = ModernPeople()  // Error
 * val consumer3 : Consumer<Food> = American()  // Error
 */
fun testConsumer() {
    val consumer1: Consumer<Burger> = Everybody()
    val consumer2: Consumer<Burger> = ModernPeople()
    val consumer3: Consumer<Burger> = American()
}


/**
 * 本例子主要是"使用处型变"
 */
class KotlinTypeTransformationDemo {

    /**
     * Collection 接口的定义 : public interface Collection<out E> : Iterable<E>
     * "out E" 就是型变的定义，表明 Collection 的元素类型是协变的，即 Collection<Number> 也是 Collection<Int> 的父类。
     * 对于 MutableList 来说，它的元素类型就是不变的： public interface MutableCollection<E> : Collection<E>, MutableIterable<E>
     * 换言之，MutableCollection<Number> 与MutableCollection<Int> 没有什么关系。
     * MutableCollection 的 addAll 的声明:public fun addAll(elements: Collection<E>): Boolean，由于
     * 参数是 Collection<E>，而 Collection 是协变的，所以传入的参数可以是任意 E 或者其子类的集合。如果下面的参数改成这样：
     * public fun addAll(elements: MutableCollection<E>): Boolean 结果就是，当 E 为 Number 时，addAll
     * 无法接类受似ArrayList<Int>的参数。而为了接受这样的参数，可以修改一下签名：
     * public fun addAll(elements: MutableCollection<out E>): Boolean
     */
    @Test
    fun testTypeTransformation() {
        var numbers: MutableList<Number> = mutableListOf(1, 2, 3, 4)
        var ints: MutableList<Int> = mutableListOf(1, 2, 3, 4)
        numbers.addAll(ints)
        println("MutableList<Number> can addAll MutableList<Int>, but MutableList<Int> cont addAll MutableList<Number>, because collection is use 'out'")

        copyArray(arrayOf(1, 2, 3), arrayOf(4, 5));
        println("copyArray have called success")

        fill(mutableListOf(1, 2, 3))
        println("fill have called success")

        successForIn(1 as Comparable<Number>)
    }


    /**
     * 类型投影：使用处型变。此处表明from不仅仅是一个数组，而是一个受限制的（投影的）数组：我们只可以调用返回类型为类型参数 T 的方法，如get()；
     * 调用 from.set() 编译异常
     */
    fun copyArray(from: Array<out Number>, to: Array<Any>) {
        from.get(1)
        to.set(0, 1)  // from.set error
    }

    /**
     * 类型投影:使用处型变。in 修饰的情况下，只可以调用其 参数类型 为类型参数T的方法。
     */
    fun fill(dest: MutableList<in String>) {
        dest.plus( "1")
        var to: Any? = dest.get(1)
    }


    /**
     * Comparable code: interface Comparable<in T> { operator fun compareTo(other: T): Int }
     *
     */
    fun successForIn(x: Comparable<Number>) {
        // 因此，我们可以将 x 赋给类型为 Comparable <Double> 的变量
        val y: Comparable<Double> = x // OK！

        x.compareTo(1.0) //error  1.0 拥有类型 Double，它是 Number 的子类型
    }
}


/**
 * 星号投影：对类型参数一无所知，但仍然希望以安全的方式使用它。所谓的安全方式就是定义泛型类型的这种投影，
 * 而该泛型类型的每个具体实例化将是该投影的子类型，即定义范型类型为所有类型的超类型——Any？，它相当于java
 * 的Object，在kotlin中用符号*代替，如：fun printList(list: MutableList<Any?>) 相当于下面的定义。
 *
 */
fun printList(list: MutableList<*>) {
    println(list[0])
}

/**
 * 范型方法：在调用 sum() 函数时，传入的参数只能是 Number 及其子类，如果是其他类型，则会报错。
 */
fun <T : Number> sum(vararg param: T) = param.sumByDouble { it.toDouble() }

/**
 * 当一个类型参数指定多个约束时，在 Java 中使用&连接多个类、接,在 Kotlin 中使用 where 关键字
 */
class MyClass<T>(var variable: Class<T>) where T : Food, T : Consumer<T>
